//package com.example.asperm
//
//import android.annotation.SuppressLint
//import android.content.ContentValues
//import android.content.Context
//import android.graphics.Bitmap
//import android.graphics.BitmapFactory
//import android.media.MediaMetadataRetriever
//import android.net.Uri
//import android.provider.MediaStore
//import android.util.Log
//import org.opencv.android.OpenCVLoader
//import org.opencv.core.*
//import org.opencv.imgcodecs.Imgcodecs
//import org.opencv.imgproc.Imgproc
//import org.opencv.video.Video
//import org.opencv.videoio.VideoCapture
//import org.opencv.videoio.Videoio
//import org.opencv.core.MatOfPoint2f
//import java.io.File
//import java.util.UUID
//import androidx.core.graphics.createBitmap
//import com.arthenica.ffmpegkit.FFmpegKit
//import com.arthenica.ffmpegkit.ReturnCode
//import kotlinx.coroutines.CoroutineScope
//import kotlinx.coroutines.Dispatchers
//import kotlinx.coroutines.launch
////import okhttp3.MediaType.Companion.toMediaTypeOrNull
////import okhttp3.MultipartBody
////import okhttp3.OkHttpClient
////import okhttp3.Request
////import okhttp3.RequestBody.Companion.asRequestBody
//import kotlinx.coroutines.withContext
//import java.io.IOException
//
//class Motility(private val context: Context, private val config: Configuration? = null) {
//
//    companion object {
//        init {
//            if (!OpenCVLoader.initDebug()) {
//                Log.e("Motility", "OpenCV initialization failed")
//            }
//        }
//    }
//
//    data class ProcessResult(val positions: List<Point>, val spermCount: Int)
//
//    fun processFrame(frame: Mat): ProcessResult {
//        val config = config ?: throw IllegalStateException("Configuration is null")
//        val kernel = Imgproc.getStructuringElement(
//            Imgproc.MORPH_ELLIPSE,
//            Size(config.grayKernalSize, config.grayKernalSize)
//        )
//
//        val gray = Mat()
//        val blurred = Mat()
//        val binary = Mat()
//        val morph = Mat()
//
//        Core.convertScaleAbs(frame, gray, config.grayAlpha, config.grayBeta)
//        Imgproc.cvtColor(gray, gray, Imgproc.COLOR_BGR2GRAY)
//        Imgproc.GaussianBlur(gray, blurred, Size(config.grayBlurredSize.toDouble(), config.grayBlurredSize.toDouble()), 0.0)
//        Imgproc.adaptiveThreshold(
//            blurred, binary, config.grayMaxValue.toDouble(),
//            Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C, Imgproc.THRESH_BINARY_INV,
//            config.grayBlockSize, config.grayCThreshold
//        )
//        Imgproc.morphologyEx(binary, morph, Imgproc.MORPH_OPEN, kernel)
//
//        return detectAndMarkSpermInFrame(frame, morph)
//    }
//
//    @SuppressLint("DefaultLocale")
//    private fun detectAndMarkSpermInFrame(frame: Mat, binaryImage: Mat): ProcessResult {
//        val config = config ?: throw IllegalStateException("Configuration is null")
//        val contours = mutableListOf<MatOfPoint>()
//        Imgproc.findContours(binaryImage, contours, Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE)
//
//        val grayImage = Mat()
//        Imgproc.cvtColor(frame, grayImage, Imgproc.COLOR_BGR2GRAY)
//
//        val positions = mutableListOf<Point>()
//        var spermCount = 0
//
//        contours.forEach { contour ->
//            val area = Imgproc.contourArea(contour)
//            val boundingBox = Imgproc.boundingRect(contour)
//
//            val sizeMatch = area >= config.regMinSize && area <= config.regMaxSize
//            val mask = Mat.zeros(grayImage.size(), CvType.CV_8UC1)
//            Imgproc.drawContours(mask, listOf(contour), -1, Scalar(255.0), Imgproc.FILLED)
//            val meanBrightness = Core.mean(grayImage, mask).`val`[0]
//            val colorMatch = meanBrightness <= config.regGrayThreshold
//
//            when {
//                sizeMatch && colorMatch -> {
//                    Imgproc.drawContours(frame, listOf(contour), -1, Scalar(0.0, 255.0, 0.0), Imgproc.FILLED)
//                    spermCount++
//                }
//                sizeMatch && !colorMatch -> {
//                    Imgproc.drawContours(frame, listOf(contour), -1, Scalar(0.0, 255.0, 255.0), Imgproc.FILLED)
//                }
//                !sizeMatch && colorMatch -> {
//                    Imgproc.drawContours(frame, listOf(contour), -1, Scalar(255.0, 0.0, 255.0), Imgproc.FILLED)
//                    Imgproc.putText(
//                        frame, String.format("%.2f", area),
//                        Point(boundingBox.x - 5.0, boundingBox.y - 5.0),
//                        Imgproc.FONT_HERSHEY_SIMPLEX, 0.3, Scalar(255.0, 255.0, 0.0), 1
//                    )
//                }
//                else -> {
//                    Imgproc.drawContours(frame, listOf(contour), -1, Scalar(0.0, 0.0, 255.0), Imgproc.FILLED)
//                    Imgproc.putText(
//                        frame, String.format("%.2f", area),
//                        Point(boundingBox.x - 5.0, boundingBox.y - 5.0),
//                        Imgproc.FONT_HERSHEY_SIMPLEX, 0.3, Scalar(255.0, 255.0, 0.0), 1
//                    )
//                }
//            }
//
//            if (sizeMatch && colorMatch && config.showSpermBox) {
//                val sideLength = maxOf(boundingBox.width, boundingBox.height)
//                val squareBox = Rect(boundingBox.x - 5, boundingBox.y - 5, sideLength + 15, sideLength + 15)
//                Imgproc.rectangle(frame, squareBox, Scalar(0.0, 0.0, 255.0), 2)
//            }
//
//            if (!config.trackSpermOnly || (sizeMatch && colorMatch)) {
//                val moments = Imgproc.moments(contour)
//                if (moments.m00 != 0.0) {
//                    val cX = moments.m10 / moments.m00
//                    val cY = moments.m01 / moments.m00
//                    positions.add(Point(cX, cY))
//                }
//            }
//        }
//
//        return ProcessResult(positions, spermCount)
//    }
//
//    private fun isEncoderSupported(encoderName: String): Boolean {
//        Log.d("Motility", "Checking for encoder: $encoderName")
//        val session = FFmpegKit.execute("-encoders")
//        if (ReturnCode.isSuccess(session.returnCode)) {
//            // Check if the output contains a line for the specified video encoder.
//            val isSupported = session.output.lines().any { it.trim().startsWith("V") && it.contains(encoderName) }
//            if (isSupported) {
//                Log.d("Motility", "Encoder '$encoderName' is supported.")
//            } else {
//                Log.e("Motility", "Encoder '$encoderName' is NOT supported.")
//            }
//            return isSupported
//        } else {
//            Log.e("Motility", "Failed to get list of encoders from FFmpeg.")
//            return false
//        }
//    }
//    @SuppressLint("DefaultLocale")
//    private fun extractVideoSegments(videoPath: String, segmentCount: Int, segmentDuration: Double): List<String> {
//        val outputPaths = mutableListOf<String>()
//        val retriever = MediaMetadataRetriever()
//        try {
//            val useLibx264 = isEncoderSupported("libx264")
//            if (!useLibx264) {
//                Log.w("Motility", "libx264 not supported. Falling back to '-c copy'. This may be inaccurate.")
//            }
//
//            Log.d("Motility", "Extracting video segments from: $videoPath")
//            retriever.setDataSource(context, Uri.parse(videoPath))
//            val durationStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
//            val totalSeconds = durationStr?.toDouble()?.div(1000.0) ?: 0.0
//
//            CoroutineScope(Dispatchers.IO).launch {
////                uploadVideoToServer(videoPath, "http://192.168.1.182:8080/upload")
//            }
//
//            val interval = (totalSeconds / segmentCount).toInt()
//            val outputDir = File(context.cacheDir, "segments").apply { mkdirs() }
//
//            // Clear previous segments
//            outputDir.listFiles()?.forEach { it.delete() }
//
//            val offset = 3
//            for (i in 0 until segmentCount) {
//                val startTime = if (i == 0) offset else i * interval
//                if (startTime >= totalSeconds) continue
//                val currentSegmentDuration = minOf(segmentDuration, totalSeconds - startTime)
//
//                val outputPath = File(outputDir, "segment_${i + 1}.mp4").absolutePath
//
//                // Use libx264 if available, otherwise fall back to copying the stream directly.
//                val ffmpegCommand = if (useLibx264) {
//                    "-y -i \"$videoPath\" -ss $startTime -t $currentSegmentDuration -c:v libx264 -preset ultrafast -c:a aac \"$outputPath\""
//                } else {
//                    "-y -i \"$videoPath\" -ss $startTime -t $currentSegmentDuration -c copy \"$outputPath\""
//                }
//
//                Log.d("Motility", "FFmpeg command: $ffmpegCommand")
//
//                val session = FFmpegKit.execute(ffmpegCommand)
//                val returnCode = session.returnCode
//                val outputFile = File(outputPath)
//                val fileSizeInBytse = if (outputFile.exists()) outputFile.length() else 0L
//
//                Log.d("Motility", "Segment $i: startTime=$startTime, duration=$currentSegmentDuration, size=${fileSizeInBytse} bytes")
//
//                if (ReturnCode.isSuccess(returnCode) && outputFile.exists() && outputFile.length() > 0) {
//                    Log.d("Motility", "Successfully created segment: $outputPath (${outputFile.length()} bytes)")
//                    outputPaths.add(outputPath)
//                } else {
//                    Log.e("Motility", "FFmpeg failed for segment $i. Return code: $returnCode")
//                    Log.e("Motility", "FFmpeg output: ${session.output}")
//                    if (outputFile.exists()) {
//                        outputFile.delete()
//                    }
//                }
//            }
//        } catch (e: Exception) {
//            Log.e("Motility", "Error extracting segments: ${e.message}")
//        } finally {
//            retriever.release()
//        }
//        return outputPaths
//    }
//
////    private suspend fun uploadVideoToServer(videoPath: String, serverUrl: String) {
////        withContext(Dispatchers.IO) {
////            try {
////                val videoFile = File(videoPath)
////                if (!videoFile.exists()) {
////                    Log.e("Motility", "Video file for upload does not exist: $videoPath")
////                    return@withContext
////                }
////
////                Log.d("Motility", "Uploading video to $serverUrl")
////
////                val client = OkHttpClient()
////                val requestBody = MultipartBody.Builder()
////                    .setType(MultipartBody.FORM)
////                    .addFormDataPart(
////                        "video",
////                        videoFile.name,
////                        videoFile.asRequestBody("video/mp4".toMediaTypeOrNull())
////                    )
////                    .build()
////
////                val request = Request.Builder()
////                    .url(serverUrl)
////                    .post(requestBody)
////                    .build()
////
////                client.newCall(request).execute().use { response ->
////                    if (response.isSuccessful) {
////                        Log.d("Motility", "Video uploaded successfully: ${response.body?.string()}")
////                    } else {
////                        Log.e("Motility", "Video upload failed: ${response.code} ${response.message}")
////                    }
////                }
////            } catch (e: IOException) {
////                Log.e("Motility", "Error uploading video: ${e.message}")
////            }
////        }
////    }
//
//
//    fun analyze(videoPath: String): MotilityAnalysisResult? {
//        val config = config ?: return null
//        Log.d("Motility", "Extracting video: $videoPath")
//        // check if the vide file exist
//        if (!File(videoPath).exists()) {
//            Log.e("Motility", "Video file does not exist: $videoPath")
//            return null
//        }
//
//        val segmentCount = config.segmentCount
//        val segmentDuration = config.segmentDuration
//        val segments = extractVideoSegments(videoPath, segmentCount, segmentDuration)
//        val results = segments.mapNotNull { analyzeSegment(it) }
//
//        val totalNumOfALevelSperms = results.sumOf { it.numOfALevelSperms }
//        val totalNumOfBLevelSperms = results.sumOf { it.numOfBLevelSperms }
//        val totalNumOfCLevelSperms = results.sumOf { it.numOfCLevelSperms }
//        val totalNumOfDetectedSperms = results.sumOf { it.numOfDetectedSperms }
//
//        return MotilityAnalysisResult(
//            avgTotalDisplacement = 0.0,
//            avgVsl = 0.0,
//            avgVcl = 0.0,
//            avgVap = 0.0,
//            avgAlh = 0.0,
//            numOfMotileTrajectories = 0,
//            numOfDetectedSperms = if (results.isNotEmpty()) totalNumOfDetectedSperms / results.size else 0,
//            numOfALevelSperms = if (results.isNotEmpty()) totalNumOfALevelSperms / results.size else 0,
//            numOfBLevelSperms = if (results.isNotEmpty()) totalNumOfBLevelSperms / results.size else 0,
//            numOfCLevelSperms = if (results.isNotEmpty()) totalNumOfCLevelSperms / results.size else 0,
//            imgData = null,
//            imgDataOfSpermDetection = null
//        )
//    }
//
//    private fun countFramesByDecoding(videoPath: String): Int {
//        val cap = VideoCapture(videoPath)
//        if (!cap.isOpened) return -1
//        val frame = Mat()
//        var count = 0
//        while (cap.read(frame)) count++
//        cap.release()
//        frame.release()
//        return count
//    }
//    private fun analyzeSegment(videoPath: String): MotilityAnalysisResult? {
//        val config = config ?: return null
//        Log.d("Motility", "Analyzing video: $videoPath")
//
////        CoroutineScope(Dispatchers.IO).launch {
////            uploadVideoToServer(videoPath, "http://192.168.1.182:8080/upload")
////        }
//
//        try {
//            Thread.sleep(2000) // Simulate delay
//        } catch (e: InterruptedException) {
//            Log.e("Motility", "Sleep interrupted: ${e.message}")
//        }
//
//        val videoCapture = VideoCapture(videoPath)
//        if (!videoCapture.isOpened) {
//            Log.e("Motility", "Couldn't read video stream from file: $videoPath")
//            return null
//        }
//
//        val totalFrames = countFramesByDecoding(videoPath)
////        videoCapture.get(Videoio.CAP_PROP_FRAME_COUNT).toInt()
//        val fps = videoCapture.get(Videoio.CAP_PROP_FPS)
//        val frameWidth = videoCapture.get(Videoio.CAP_PROP_FRAME_WIDTH)
//        val frameHeight = videoCapture.get(Videoio.CAP_PROP_FRAME_HEIGHT)
//
//        val cropWidth = (frameWidth * 0.5).toInt()
//        val cropHeight = (frameHeight * 0.9).toInt()
//        val xStart = ((frameWidth - cropWidth) / 2).toInt()
//        val yStart = ((frameHeight - cropHeight) / 2).toInt()
//
//        val fgbg = Video.createBackgroundSubtractorMOG2()
//        val spermTracks = mutableMapOf<Int, MutableList<Point>>()
//        var spermID = 0
//
//        val outputPath = File(context.cacheDir, "asperm.jpg").absolutePath
//        val outputPath2 = File(context.cacheDir, "asperm_detection.jpg").absolutePath
//        var outputFrame: Mat? = null
//
//        var sumOfSperms = 0
//        var sumOfSpermDetections = 0
//
//        for (frameNum in 0 until totalFrames) {
//            val frame = Mat()
//            if (!videoCapture.read(frame)) break
//
//            val processedFrame = if (config.cropFrame) {
//                Mat(frame, Rect(xStart, yStart, cropWidth, cropHeight))
//            } else frame
//
//            val result = processFrame(processedFrame)
//            val spermPositionsInFrame = result.positions
//            val spermCount = result.spermCount
//
//            if (spermPositionsInFrame.isNotEmpty()) {
//                sumOfSperms += spermPositionsInFrame.size
//                sumOfSpermDetections++
//
//                spermPositionsInFrame.forEach { pos ->
//                    var matched = false
//                    spermTracks.forEach { (id, track) ->
//                        if (track.isNotEmpty()) {
//                            val distance = Core.norm(MatOfPoint2f(track.last(), pos))
//                            if (distance < config.matchDistance) {
//                                track.add(pos)
//                                matched = true
//                                return@forEach
//                            }
//                        }
//                    }
//                    if (!matched) {
//                        spermTracks[spermID++] = mutableListOf(pos)
//                    }
//                }
//
//                spermTracks.forEach { (_, points) ->
//                    if (points.isNotEmpty()) {
//                        points.windowed(2).forEach { (prev, curr) ->
//                            Imgproc.line(processedFrame, prev, curr, Scalar(0.0, 255.0, 0.0), 2)
//                            Imgproc.circle(processedFrame, curr, 3, Scalar(0.0, 0.0, 255.0), -1)
//                        }
//                    }
//                }
//
//                outputFrame = processedFrame
//            }
//        }
//
//        videoCapture.release()
//        outputFrame?.let { Imgcodecs.imwrite(outputPath2, it) }
//
//        val motileTrajectories = mutableListOf<DoubleArray>()
//        spermTracks.forEach { (_, points) ->
//            if (points.size > 1) {
//                var totalDisplacement = 0.0
//                points.windowed(2).forEach { (prev, curr) ->
//                    totalDisplacement += Core.norm(MatOfPoint2f(prev, curr))
//                }
//
//                val startPos = points.first()
//                val endPos = points.last()
//                val straightLineDistance = Core.norm(MatOfPoint2f(startPos, endPos))
//                val totalTime = points.size / fps
//                val vsl = straightLineDistance / totalTime
//                val vcl = totalDisplacement / totalTime
//
//                val averagePath = points.fold(Point(0.0, 0.0)) { acc, pos ->
//                    Point(acc.x + pos.x, acc.y + pos.y)
//                }.let { Point(it.x / points.size, it.y / points.size) }
//
//                val totalAveragePathDistance = points.sumOf { Core.norm(MatOfPoint2f(it, averagePath)) }
//                val vap = totalAveragePathDistance / totalTime
//
//                val alh = points.sumOf { Core.norm(MatOfPoint2f(it, averagePath)) } / points.size
//
//                motileTrajectories.add(doubleArrayOf(totalDisplacement, vsl, vcl, vap, alh))
//            }
//        }
//
//        var avgTotalDisplacement = 0.0
//        var avgVsl = 0.0
//        var avgVcl = 0.0
//        var avgVap = 0.0
//        var avgAlh = 0.0
//        val pixelRatio = 0.5
//
//        var numOfALevelSperms = 0
//        var numOfBLevelSperms = 0
//        var numOfCLevelSperms = 0
//
//        motileTrajectories.forEach { trajectory ->
//            avgTotalDisplacement += trajectory[0]
//            avgVsl += trajectory[1]
//            avgVcl += trajectory[2]
//            avgVap += trajectory[3]
//            avgAlh += trajectory[4]
//
//            val vsl = trajectory[1] / pixelRatio
//            val vcl = trajectory[2] / pixelRatio
//            val lin = if (vcl > 0) vsl / vcl else 0.0
//
//            when {
//                lin >= 0.6 && vcl > 40 -> numOfALevelSperms++
//                vcl in 20.0..40.0 -> numOfBLevelSperms++
//                vcl > 40 && lin < 0.6 -> numOfCLevelSperms++
//            }
//        }
//
//        if (motileTrajectories.isNotEmpty()) {
//            avgTotalDisplacement /= motileTrajectories.size
//            avgVsl /= motileTrajectories.size
//            avgVcl /= motileTrajectories.size
//            avgVap /= motileTrajectories.size
//            avgAlh /= motileTrajectories.size
//
//            avgTotalDisplacement /= pixelRatio
//            avgVsl /= pixelRatio
//            avgVcl /= pixelRatio
//            avgVap /= pixelRatio
//            avgAlh /= pixelRatio
//        }
//
//        val numOfDetectedSperms = if (sumOfSpermDetections > 0) sumOfSperms / sumOfSpermDetections else 0
//        val imgDataOfSpermDetection = outputFrame?.let {
//            createBitmap(it.cols(), it.rows()).apply {
//                org.opencv.android.Utils.matToBitmap(it, this)
//            }
//        }
//
//        val result = MotilityAnalysisResult(
//            avgTotalDisplacement, avgVsl, avgVcl, avgVap, avgAlh,
//            motileTrajectories.size, numOfDetectedSperms,
//            numOfALevelSperms, numOfBLevelSperms, numOfCLevelSperms,
//            null, imgDataOfSpermDetection
//        )
//
//        if (config.saveCapturedVideoToPhotos) {
//            saveVideoToPhotos(videoPath)
//        }
//
//        if (config.saveProceedImgsToPhotos) {
//            saveImgToPhotos(outputPath)
//            saveImgToPhotos(outputPath2)
//        }
//
//        return result
//    }
//
//    private fun saveVideoToPhotos(videoPath: String) {
//        try {
//            val values = ContentValues().apply {
//                put(MediaStore.Video.Media.DISPLAY_NAME, "asperm_${UUID.randomUUID()}.mp4")
//                put(MediaStore.Video.Media.MIME_TYPE, "video/mp4")
//                put(MediaStore.Video.Media.DATA, videoPath)
//            }
//            context.contentResolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values)
//            Log.d("Motility", "Saved video to gallery")
//        } catch (e: Exception) {
//            Log.e("Motility", "Failed to save video: ${e.message}")
//        }
//    }
//
//    private fun saveImgToPhotos(imgPath: String) {
//        try {
//            val bitmap = BitmapFactory.decodeFile(imgPath)
//            if (bitmap != null) {
//                val values = ContentValues().apply {
//                    put(MediaStore.Images.Media.DISPLAY_NAME, "asperm_${UUID.randomUUID()}.jpg")
//                    put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
//                    put(MediaStore.Images.Media.DATA, imgPath)
//                }
//                context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
//                Log.d("Motility", "Saved image to gallery")
//            } else {
//                Log.e("Motility", "Cannot load image from: $imgPath")
//            }
//        } catch (e: Exception) {
//            Log.e("Motility", "Failed to save image: ${e.message}")
//        }
//    }
//}